<?php

/** this file is not a valid entry point */ 
if ( !defined( 'MEDIAWIKI' ) ) {
	die( 'This file is a MediaWiki extension, it is not a valid entry point' );
}

/** sets our config hook */
$wgHooks['ParserFirstCallInit'][] = 'cgParserInit';

/** adds extension to credits in Special:Version */
$wgExtensionCredits['parserhook'][] = array(
	'path' => __FILE__,
	'name' => "GoogleChart",
	'description' => "Adds Google Charts support to wikitext",
	'version' => 1,
	'author' => "431 MW Group", //let's not put realnames just yet since we'll be pasting this online
	//'url' => "",
);

//******************************************************************************
// Configuration variables
//******************************************************************************

/** array of valid keys per chart */
$cgChartKeyNames = array(
	'type',
	'align',	
	'xlabel',
	'ylabel',
	'xmin',
	'xmax',
	'ymin',
	'ymax',
	'xscale',
	'yscale',
);

/** array of valid keys per record */
$cgRecordKeyNames = array(
	'label',
	'width',
	'height',
	'data',
	'padding',
	'marker',
	'color',
);

/** array of valid chart types */
$cgChartTypes = array(
	'line',
	'bar',
	'plot',
	'pie',
);

/** array of default key:value pairs */
$cgDefaultPairs = array(
	'chs'  => '200x200',
	'chd'  => 't:10,20,30',
	'cht'  => 'bvs',
	'chxt' => 'x,y',
);

/** array of colors we know */
$cgColorMap = array(
	'black' => '000000',
	'white' => 'ffffff',
	'red'   => 'ff0000',
	'green' => '00ff00',
	'blue'  => '0000ff',
	'yellow'=> 'FFE303',
	'brown' => '603311',
	'grey'	=> 'B6AFA9',
	'gray'  => 'B6AFA9',
);

//******************************************************************************
// cgChart class
//******************************************************************************

/** sets up the extension, must return true */
function cgParserInit( &$parser )
{
	$parser->setHook( 'chart', 'cgRender' );
	return true;
}

//******************************************************************************
// Chartgroup parser hook
//******************************************************************************

/**
 * MAIN HOOK for the <chart> tag
 *
 * @param $input - string, contents of <chart>...</chart> excluding tags
 * @param $args - associative array of arguments in the <chart> tag
 * @param $parser - current mediawiki parser object
 * @param $frame - current mediawiki frame object
 * @return - string, html to dump into rendered page
 */
function cgRender( $input, $args, $parser, $frame )
{
	//strip newlines
	$input = preg_replace( "/\n/", '', $input );
	
	$records = cgParseRecords( $input );
	
	$output = '';
	//$output .= cgDumpValues( $args, $records  ) . '<br/><br/>';
	$output .= cgChart( $args, $records );
	$output .= '<br/><br/>' . htmlspecialchars( cgChart( $args, $records ) );
	
	return $output;
}

/**
 * parses chart input into an array of key:value pairs
 *
 * @param input - raw input string to be parsed
 * @return - 2d array: $records[recordnum][key] => [value]
 */
function cgParseRecords( $input )
{
	//split chart into records
	trim( $input );
	$recordsRaw = preg_split( '/;;/', $input, 0, PREG_SPLIT_NO_EMPTY );
	
	//for each record...
	for( $i = 0; $i < count( $recordsRaw ); $i++ )
	{
		//split record into pairs
		$pairsRaw = preg_split( '/;/', $recordsRaw[ $i ], 0, PREG_SPLIT_NO_EMPTY );
		
		//for each pair in this record...
		foreach ( $pairsRaw as $rawpair )
		{
			//split pair into key:value
			$parts = preg_split( '/:/', $rawpair, 2, PREG_SPLIT_NO_EMPTY );
			
			//case: only one part -- no key set, assume anonymous value
			if ( count( $parts ) == 1 )
			{
				$key = 'data';
				$value = $parts[ 0 ];
			}

			//case: two parts -- normal key:value
			else
			{
				$key = $parts[ 0 ];
				$value = $parts[ 1 ];
			}
			
			//strip whitespace and caps
			$key = trim( $key );
			$key = strtolower( $key );
			$value = trim( $value ); //value only strips space, not caps
			
			//insert data into array
			$records[ $i ][ $key ] = $value;
		}
	}
	
	return $records;
}

/**
 * converts array input to chart image url
 *
 * @param $args - array of tag arguments, values as follow: 
 * -size : size of the image in pixels
 * -type : type of chart, bar, scatter, pie and line currently implemented
 * -title : title of the chart
 * -xtitle : x axis title
 * -ytitle : y axis title
 * -background : background color
 * -angle : pie chart specific argument to rotate the chart around on its axis, takes angle in degrees.  
 * @param $records - array of records (2d!)
 * @return - html to display chart
 */
function cgChart( $args, $records )
{
	global $cgDefaultPairs;
	
	//assume default params
	$elements = $cgDefaultPairs;

	//arg: size
	if ( $args[ 'size' ] && preg_match('/\d+x\d+/', $args[ 'size' ] ) )
	{
		$elements[ 'chs' ] = $args[ 'size' ];
	}
	
	//arg: type
	if ( $args[ 'type' ] == 'bar' )
	{
		$elements[ 'cht' ] = 'bvs';
	}
	else if ( $args[ 'type' ] == 'line' )
	{
		$elements[ 'cht' ] = 'lc';
	}
	else if ( $args[ 'type' ] == 'plot' )
	{
		$elements[ 'cht' ] = 's';
	}
	else if ( $args[ 'type' ] == 'pie' )
	{
		$elements[ 'cht' ] = 'p';
	}
	else if ( $args[ 'type' ] == '3Dpie' )
	{
		$elements[ 'cht' ] = 'p3';
	}
	
	//arg: title
	if ( $args[ 'title' ] )
	{
		$elements[ 'chtt' ] = urlencode( $args[ 'title' ] );
	}
	
	//arg: background
	if ( $args[ 'background' ] )
	{
		$elements[ 'chf' ] = 'c,s,' . cgParseColor( $args[ 'background' ] );
	}
	
	//arg: x range AND y range
	if ( $args[ 'xrange' ] && $args[ 'yrange' ] )
	{
		$elements[ 'chxr' ] = '0,' . $args[ 'xrange' ] . '|1,' . $args[ 'yrange' ];
	}
	else if ( $args[ 'xrange' ] )
	{
		$elements[ 'chxr' ] = '0,' . $args[ 'xrange' ];
	}
	else if ( $args['yrange'] )
	{
		$elements[ 'chxr' ] = '1,' . $args[ 'yrange' ];
	}
	
	//PARSE RECORDS, one by one
	for ( $i = 0; $i < count( $records ); $i++ )
	{
		$record = $records [ $i ];
		
		//collect info
		$data[ $i ] = $record[ 'data' ];
		$labels[ $i ] = urlencode( $record[ 'label' ] );
		if ( $record[ 'color' ] ) $colors[$i] = cgParseColor($record[ 'color' ]);
		
		//check min/max
		$values = preg_split( '/,/', $data[ $i ] );
		foreach ( $values as $val )
		{
			if ( !isset( $min ) || $val < $min ) $min = $val;
			if ( !isset( $max ) || $val > $max ) $max = $val;
		}
	}
	
	//bar charts
	if ( $elements[ 'cht' ] == 'bvs' )
	{
		if ( $data ) $elements[ 'chd' ] = 't:' . join( ',', $data );
		if ( $labels ) $elements[ 'chxl' ] = '0:|' . join( '|', $labels );
		if ( $colors ) $elements[ 'chco' ] = join( '|', $colors );
	}
	
	//scatter charts
	if ( $elements[ 'cht' ] == 's' )
	{
		$x;
		$y;
		for ($i=0;$i<count($data); $i++)
			{
			preg_match( "/(\d+),(\d+)/", $data[ $i ], $matches );
			$x[$i]= $matches[ 1 ];
			$y[$i] = $matches[ 2 ];
			}
		$xlist=join(',', $x);
		$ylist=join(',',$y);
		if ( $data ) $elements[ 'chd' ] = 't:' . $xlist . '|' . $ylist;
		//if ( $labels ) $elements[ 'chxl' ] = '0:|1:|'; //. join( '|', $labels );
		if ( $colors ) $elements[ 'chco' ] = join( '|', $colors );
	}	
	
	//pie charts
	if ( $elements[ 'cht' ] == 'p' || $elements[ 'cht' ] == 'p3' )
	{
		if ( $data ) $elements[ 'chd' ] = 't:' . join( ',', $data );
		if ( $labels ) $elements[ 'chdl' ] = join( '|', $labels );
		if ( $colors ) $elements[ 'chco' ] = join( '|', $colors );
		//chxt isn't a valid parameter for pie charts, but the api ignores it when nulled, so that is what we do.
		$elements['chxt']="";
		//pie chart specific argument angle, takes an angle in degrees and converts it to radians and changes the orientation of the chart by that much.
		if ( $args[ 'angle' ] ) 
		{ 
		  $elements[ 'chp' ] = $args[ 'angle' ] / 180; 
		}
	}
		
	//line charts
	if ( $elements[ 'cht' ] == 'lc' )
	{
		if ( $data ) $elements[ 'chd' ] = 't:' . join( '|', $data );
		if ( $labels ) $elements[ 'chdl' ] = join( '|', $labels );
		if ( $colors ) $elements[ 'chco' ] = join( ',', $colors );
	}

	//args: xtitle and ytitle
	if ( $args['xtitle'] )
	{
		//$elements['chxt'] .=',x';
		$chxl[] = $args[ 'xtitle' ];
		//$elements['chxl'] .='|2:|'.$args['xtitle'];
	}
	if ($args['ytitle'])
	{
		//$elements['chxt'] .=',y';
		$chxl[] = $args[ 'ytitle' ];
		//if($elements['chxl'] ) $elements['chxl'].='|3:|'.$args['ytitle'];
		//else $elements['chxl']='3:|'.$args['ytitle'];
	}
	
	//apply data range
	if (!( $elements[ 'cht' ] == 'p' || $elements[ 'cht' ] == 'p3' ))
	{
		$elements[ 'chds' ] = $min . ',' . $max;
		$elements[ 'chxr' ] = '1,' . $min . ',' . $max;
	}
	
	//apply axis labels
	unset ( $elements[ 'chxl' ] );
	for ( $j = 0; $j < count( $chxl ); $j++ )
	{
		if ( $j > 0 ) $elements[ 'chxl' ] .= '|';
		$elements[ 'chxl' ] .= $j . ':|' . $chxl[ $j ];
	}
	
	//build our output string!
	$output = '<img src="http://chart.apis.google.com/chart?';
	foreach ( $elements as $key => $value )
	{
		$output .= $key . '=' . $value . '&';
	}
	$output = preg_replace( '/&$/', '', $output );
	$output .= '" />';
	
	return  $output;
}

/**
 * dumps input for cgChart
 * @param $args - array of tag arguments
 * @param $records - array of records (2d!)
 * @return - url
 */
function cgDumpValues( $args, $records )
{
	$output = "CHART<br/>";
	
	//dump chart args
	foreach( $args as $key => $value )
	{
		$output .= "($key)($value)<br/>";
	}
	
	//dump record args
	foreach( $records as $num => $pairs )
	{
		$output .= "RECORD $num<br/>";
		
		foreach ( $pairs as $key => $value )
		{
			
			$output .= "($key)($value)<br/>";;
		}
	}
	return $output;
}

function keymod($key,$value)
{
	//key: type
	if ( $key == 'type' ) {
		switch ( $value )
		{
			case 'bar':
				$output = 'cht=bvs';
				break;
			case 'line':
				$output = 'cht=lc';
				break;
			case 'plot':
				$output = 'cht=s';
				break;
		}
	}
	
	//key: data (aka 'anon')
	else if ( $key == 'data' || $key == 'anon' )
	{
		$output = 'chd=t:' . $value;
	}
	
	//key: color
	else if ( $key == 'color' )
	{
		$output = 'chco=' . cgParseColor( $value );;
	}
	
	//key: size
	else if ( $key == 'size' )
	{
		$output = 'chs=' . $value;
	}

	return $output;
}

/**
 * checks if a key is in a given key list
 *
 * @param $keyname - string, key to check for validity
 * @param $keylist - array, canonical key list to check against
 */
function cgIsValidKey( $keyname, &$keylist )
{
	return ( array_search( $keyname, $keylist ) !== false );
}

/** is given key valid for a record? */
function cgIsValidRecordKey( $keyname )
{
	global $cgRecordKeyNames;
	return cgIsValidKey( $keyname, $cgRecordKeyNames );
}

/** is given key valid for a chart? */
function cgIsValidChartKey( $keyname )
{
	global $cgChartKeyNames;
	return cgIsValidKey( $keyname, $cgChartKeyNames );
}

/** maps user produced color to one that google can read */
function cgParseColor( $raw )
{
	global $cgColorMap;
	
	//check if we got a #RRGGBBaa color
	if ( preg_match( "/#?([0-9a-fA-F]{6}([0-9a-fA-F]{2})?)/", $raw, $matches ) )
	{
		return $matches[ 1 ];
	}
	
	//otherwise check if it's in our color list
	else if ( $cgColorMap[ $raw ] != null )
	{
		return $cgColorMap[ $raw ];
	}
	
	//otherwise just give them black
	else
	{
		return $cgColorMap[ 'black' ];
	}
}